
/* GCSx
** FILE.CPP
**
** Basic file access with exceptions
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

string* mainDir = NULL;
string* resourceDir = NULL;

File::File(const char* fFilename, int fMode) throw_File { start_func
    filename = fFilename;
    mode = fMode;
    if (fMode == FILE_MODE_OVERWRITE) {
        filePtr = fopen(filename, "w+b");
        mode = FILE_MODE_READWRITE;
    }
    else {
        filePtr = fopen(filename, fMode == FILE_MODE_READ ? "rb" : "r+b");
    }
    if (!filePtr) {
        throw FileException("Unable to load file %s: %s", filename, strerror(errno));
    }
    lastOperation = OPER_SEEK;
    offset = ftell(filePtr);
}

File::~File() { start_func
    if (filePtr) fclose(filePtr);
}

char File::copyBuffer[COPY_BUFFER_SIZE + 1];

void File::reseek() { start_func
    assert(filePtr);

    fseek(filePtr, 0, SEEK_CUR);
}

/*
int File::readStr(string& str) throw_File { start_func
    assert(filePtr);

    // (sets read mode)
    int size = readInt16();
    int origSize = size;
    int first = 1;

    if (size) {
        while (size) {
            int amount = min(size, COPY_BUFFER_SIZE);
            read(copyBuffer, amount);
            copyBuffer[amount] = 0;
            if (first) str = copyBuffer;
            else str += copyBuffer;
            size -= amount;
        }
    }
    else {
        str = blankString;
    }
    
    return origSize + 2;
}
*/

void File::read(void* ptr, int size) throw_File { start_func
    assert(size >= 0);
    assert(filePtr);
    assert(ptr);
    
    if (size) {
        if (lastOperation == OPER_WRITE) reseek();
        lastOperation = OPER_READ;
        if (fread(ptr, size, 1, filePtr) != 1) {
            offset = ftell(filePtr);
            if (ferror(filePtr)) {
                throw FileException("File read error: %s", strerror(errno));
            }
            else {
                throw FileException("Unexpected end-of-file");
            }
        }
        
        offset += size;
    }
}

Uint32 File::readInt() throw_File { start_func
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
    Uint32 result;
    read(&result, 4);
    return result;
#else
    Uint8 bytes[4];
    read(bytes, 4);
    return (Uint32)bytes[0] | ((Uint32)bytes[1] << 8) | ((Uint32)bytes[2] << 16) | ((Uint32)bytes[1] << 24);
#endif
}

/*
Uint16 File::readInt16() throw_File { start_func
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
    Uint16 result;
    read(&result, 2);
    return result;
#else
    Uint8 bytes[2];
    read(bytes, 2);
    return (Uint16)bytes[0] | ((Uint16)bytes[1] << 8);
#endif
}
*/

/*
int File::writeStr(const string& str) throw_File { start_func
    assert(filePtr);

    // (sets write mode)
    writeInt16(str.size());
    write(str.c_str(), str.size());
    
    return str.size() + 2;
}
*/

void File::write(const void* ptr, int size) throw_File { start_func
    assert(size >= 0);
    assert(filePtr);
    assert(ptr);
    
    if (size) {
        if (lastOperation == OPER_READ) reseek();
        lastOperation = OPER_WRITE;
    
        if (fwrite(ptr, size, 1, filePtr) == 0) {
            offset = ftell(filePtr);
            throw FileException("File write error: %s", strerror(errno));
        }
    
        offset += size;
    }
}

void File::writeInt(Uint32 value) throw_File { start_func
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
    write(&value, 4);
#else
    Uint8 bytes[4];
    bytes[0] = value & 0xFF;
    bytes[1] = (value >> 8) & 0xFF;
    bytes[2] = (value >> 16) & 0xFF;
    bytes[3] = (value >> 24) & 0xFF;
    write(bytes, 4);
#endif
}

/*
void File::writeInt16(Uint16 value) throw_File { start_func
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
    write(&value, 2);
#else
    Uint8 bytes[2];
    bytes[0] = value & 0xFF;
    bytes[1] = (value >> 8) & 0xFF;
    write(bytes, 2);
#endif
}
*/

void File::writeBlanks(int size) throw_File { start_func
    assert(size >= 0);
    assert(filePtr);
    
    if (size) {
        if (lastOperation == OPER_READ) reseek();
        lastOperation = OPER_WRITE;
        
        while (size--) {
            if (fputc(0, filePtr) == EOF) {
                throw FileException("File write error: %s", strerror(errno));
            }
            ++offset;
        }
    }
}

void File::copy(File* copyFrom, int size) throw_File { start_func
    assert(size >= 0);
    assert(filePtr);
    assert(copyFrom);
    
    if (size) {
        if (lastOperation == OPER_READ) reseek();
        lastOperation = OPER_WRITE;
        
        while (size > COPY_BUFFER_SIZE) {
            copyFrom->read(copyBuffer, COPY_BUFFER_SIZE);
            write(copyBuffer, COPY_BUFFER_SIZE);
            size -= COPY_BUFFER_SIZE;
        }
        
        if (size) {
            copyFrom->read(copyBuffer, size);
            write(copyBuffer, size);
        }
    }
}

void File::flush() throw_File { start_func
    if (fflush(filePtr)) {
        throw FileException("File flush error: %s", strerror(errno));
    }    
}

void File::skip(int size) throw_File { start_func
    assert(filePtr);

    if (fseek(filePtr, size, SEEK_CUR)) {
        offset = ftell(filePtr);
        throw FileException("File seek error: %s", strerror(errno));
    }

    lastOperation = OPER_SEEK;
    offset += size;
}

void File::seek(int position) throw_File { start_func
    assert(filePtr);
    
    if (offset != position) {
        if (fseek(filePtr, position, SEEK_SET)) {
            offset = ftell(filePtr);
            throw FileException("File seek error: %s", strerror(errno));
        }

        lastOperation = OPER_SEEK;
        offset = position;
    }
}

int File::tell() const { start_func
    assert(filePtr);
    
    return offset;
}

const char* topDir() { start_func
    return FILESYSTEM_ABSOLUTEPREFIX;
}

int findFiles(const char* rootdir, const char* match, vector<string>& result) { start_func
    assert(rootdir);
    string searchterm;
    
    if ((FILESYSTEM_DRIVE_SEP) && (!myStricmp(rootdir, FILESYSTEM_ABSOLUTEPREFIX))) {
#ifdef WIN32
        // Return all drives, but only on WIN32/similar
        if (match) return 0;
        
        Uint32 drives = GetLogicalDrives();
        char driveName[3] = "A?";
        driveName[1] = FILESYSTEM_DRIVE_SEP;
        while (drives) {
            if (drives & 1) result.push_back(driveName);
            ++driveName[0];
            drives = drives >> 1;
        }
        
        return 1;
#endif

        // Any other OS that supports drives?
        return 0;
    }

    int matches = 0;
    
    DIR* dirPtr;
    
    // Drives must be opened in a special way
    if ((FILESYSTEM_DRIVE_SEP) && (rootdir[0] != 0) && (rootdir[1] == FILESYSTEM_DRIVE_SEP) && (rootdir[2] == 0)) {
        char drivedir[strlen(rootdir) + strlen(FILESYSTEM_PREFERREDSEP)];
        strcpy(drivedir, rootdir);
        strcat(drivedir, FILESYSTEM_PREFERREDSEP);
        dirPtr = opendir(drivedir);
    }
    else {
        dirPtr = opendir(rootdir);
    }
    
    if (!dirPtr) return 0;
    
    struct dirent* entry;
    struct stat inode;
    
    int matchlen;
    if (match) matchlen = strlen(match);
    
    // (assignment intentional)
    while ( (entry = readdir(dirPtr)) ) {
        // Skip entries beginning with .
        if (entry->d_name[0] == '.') continue;
    
        string fullpath;
        createFilename(rootdir, entry->d_name, fullpath);

        // Skip entries we can't stat
        if (stat(fullpath.c_str(), &inode) < 0) continue;
    
        if (match) {
            // @TODO: We could also support links- if (S_ISLNK (inode.st_mode))
            if (S_ISREG (inode.st_mode)) {
                // Match extension to end of string; always case insensitive
                int len = strlen(entry->d_name);
                if (len > matchlen) {
                    if (!myStricmp(entry->d_name + (len - matchlen), match)) {
                        ++matches;
                        result.push_back(entry->d_name);
                    }
                }
            }
        }
        else {
            if (S_ISDIR (inode.st_mode)) {
                ++matches;
                result.push_back(entry->d_name);
            }
        }
    }
    
    closedir(dirPtr);

    return matches;
}

int existFiles(const char* rootdir, const char* match) { start_func
    assert(rootdir);
    string searchterm;

    // For drives, always return "yes" to whether subdirectories exist
    // to prevent needless drive accesses
    if (FILESYSTEM_DRIVE_SEP) {
        // Also, if we have drives, topdir has drives in it
        if (!myStricmp(rootdir, FILESYSTEM_ABSOLUTEPREFIX)) {
            if (match) return 0;
            return 1;
        }
        
        if ((rootdir[0] != 0) && (rootdir[1] == FILESYSTEM_DRIVE_SEP) && (rootdir[2] == 0) && (match == NULL)) return 1;
    }

    DIR* dirPtr = opendir(rootdir);
    if (!dirPtr) return 0;
    
    struct dirent* entry;
    struct stat inode;

    int matchlen;
    if (match) matchlen = strlen(match);

    // @TODO: update GUI/show delay in some way (stat'ing a large network directory, etc.)
    // (assignment intentional)
    while ( (entry = readdir(dirPtr)) ) {
        // Skip entries beginning with .
        if (entry->d_name[0] == '.') continue;
    
        string fullpath;
        createFilename(rootdir, entry->d_name, fullpath);

        // Skip entries we can't stat
        if (stat(fullpath.c_str(), &inode) < 0) continue;
    
        if (match) {
            // #TODO: We could also support links- if (S_ISLNK (inode.st_mode))
            if (S_ISREG (inode.st_mode)) {
                // Match extension to end of string; always case insensitive
                int len = strlen(entry->d_name);
                if (len > matchlen) {
                    if (!myStricmp(entry->d_name + (len - matchlen), match)) {
                        closedir(dirPtr);
                        return 1;
                    }
                }
            }
        }
        else {
            if (S_ISDIR (inode.st_mode)) {
                closedir(dirPtr);
                return 1;
            }
        }
    }

    closedir(dirPtr);

    return 0;
}

void createDirname(const char* rootdir, const char* subdir, string& newRootdir) { start_func
    assert(rootdir);
    assert(subdir);

    newRootdir = rootdir;
    // Only add separator if not absolute root directory
    if (myStricmp(rootdir, FILESYSTEM_ABSOLUTEPREFIX)) newRootdir += FILESYSTEM_PREFERREDSEP;
    newRootdir += subdir;
}

void createFilename(const char* rootdir, const char* filename, string& fullFilename) { start_func
    assert(rootdir);
    assert(filename);
    
    // Absolute path if starts with any separator
    if ((strchr(FILESYSTEM_SEPARATORS, filename[0])) || 
        // Also an absolute path if begins with a drive
        ((FILESYSTEM_DRIVE_SEP) && (filename[0] != 0) && (filename[1] == FILESYSTEM_DRIVE_SEP))
        ) {
        fullFilename = filename;
    }
    
    // Relative path- add to rootdir
    else {
        createDirname(rootdir, filename, fullFilename);
    }
}

void splitFilename(const char* filename, vector<string>& result) { start_func
    assert(filename);
    
    string file = filename;
    string::size_type prev = 0;
    string::size_type found = 0;
    
    while (1) {
        found = file.find_first_of(FILESYSTEM_SEPARATORS, found);

        if (found >= string::npos) {
            result.push_back(file.substr(prev));
            break;
        }
        
        if (found > prev) {
            result.push_back(file.substr(prev, found - prev));
        }

        ++found;
        prev = found;
    }
}

void getPathname(const char* filename, string& result) { start_func
    assert(filename);

    string file = filename;

    string::size_type found = file.find_last_of(FILESYSTEM_SEPARATORS);
    if (found >= string::npos) result = FILESYSTEM_ABSOLUTEPREFIX;
    else result = file.substr(0, found);
}

